package com.lw.nio.selector;

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Iterator;

/**
 * NIO服务端
 * @author LW
 * @2021/9/13 20:39
 */
public class NIOServer {
    // 通道管理器
    private Selector selector;

    /**
     * 启动服务端测试
     */
    public static void main(String[] args) throws IOException {
        NIOServer server = new NIOServer();
        server.initServer(8000);
        server.listen();
    }

    /**
     * 获得一个ServerSocket通道，并对该通道做一些初始化的工作
     */
    public void initServer(int port) throws IOException {
        // 获得一个ServerSocket通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 设置通道为非阻塞
        serverChannel.configureBlocking(false);

        // 将该通道对应的ServerSocket绑定到port端口
        serverChannel.socket().bind(new InetSocketAddress(port));

        // 获得一个通道管理器 //selector的酒店招聘的服务员，我们需要酒店告诉服务员做什么事情，对什么样的网络事件感兴趣
        this.selector = Selector.open();
        // 将通道管理器和该通道绑定，并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后，
        // 当该事件到达时，selector.select()会返回，如果该事件没到达selector.select()会一直阻塞。
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);//观察门口有没有人进来
    }

    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件，如果有，则进行处理
     */
    public void listen() throws IOException {
        System.out.println("服务端启动成功！");
        // 轮询访问selector
        while (true) {
            // 当注册的事件到达时，方法返回；否则,该方法会一直阻塞
            selector.select();//int client = selector.select()
            // 获得selector中选中的项的迭代器，选中的项为注册的事件 // selector中有keys，key中有事件类型，比如key.isAcceptable()或者key.isReadable()
            Iterator<?> ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                // 拿到当前事件key
                SelectionKey key = (SelectionKey) ite.next();
                // 删除已选的key,以防重复处理
                ite.remove();

                // 分发处理事件 // dispatch()
                handler(key);
            }
        }
    }

    /**
     * 处理请求
     */
    public void handler(SelectionKey key) throws IOException {
        // 如果是客户端请求连接事件
        if (key.isAcceptable()) {
            handlerAccept(key);// 把客户端的通道注册到selector上

        } else if (key.isReadable()) { // 获得了可读的事件 // 客户端通道已经读了，你读到buffer吧
            handelerRead(key);
        }
        // Runnable r = (Runnable)(key.attachment()); // 去除的是new Acceptor(selector,ssc)
        // if(r!=null) r.run();
    }

    /**
     * 处理连接请求，相当于BIO的socket.accept
     */
    public void handlerAccept(SelectionKey key) throws IOException {
        // key里拿到服务端socket
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        // 获得和客户端连接的通道
        SocketChannel channel = server.accept();
        // 设置成非阻塞
        channel.configureBlocking(false);

        // 在这里可以给客户端发送信息哦
        System.out.println("新的客户端连接");
        // 在和客户端连接成功之后，为了可以接收到客户端的信息，需要给通道设置读的权限。
        channel.register(this.selector, SelectionKey.OP_READ);
    }

    /**
     * 处理读的事件，相当于BIO的socket.read
     */
    public void handelerRead(SelectionKey key) throws IOException {
        // 服务器可读取消息:得到事件发生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 创建读取的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int read = channel.read(buffer);
        if(read > 0){
            byte[] data = buffer.array();
            String msg = new String(data).trim();
            System.out.println("服务端收到信息：" + msg);

            //回写数据
            ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());
            channel.write(outBuffer);// 将消息回送给客户端
        }else{
            System.out.println("客户端关闭");
            key.cancel();//关闭通道，这里不合理，应该放到try catch中
        }
    }

    /**
     * 客户端
     * @throws IOException
     */

    @Test //读取本地文件发送到服务端 //非阻塞模式完成客户端向服务器端传输数据
    public void client() throws IOException {

        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",7498));

        // 切换成非 阻塞模式
        socketChannel.configureBlocking(false);

        // 创建一个输入通道
        FileChannel inputChannel = FileChannel.open(Paths.get("C:\\Users\\ASUS\\Desktop\\gdmp.txt"), StandardOpenOption.READ);

        //分配缓存区
        ByteBuffer clientBuffer = ByteBuffer.allocate(1024);

        //读取本地文件发送到服务端
        while (inputChannel.read(clientBuffer) != -1){
            clientBuffer.flip();
            socketChannel.write(clientBuffer);// 输入通道读到buffer中，buffer再写到网络通道中
            clientBuffer.clear();
        }

        socketChannel.close();
        inputChannel.close();
    }

    /**
     * 服务端
     * @throws IOException
     */

    @Test // 服务器接收文件
    public void server() throws IOException {

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 非阻塞
        serverSocketChannel.configureBlocking(false);

        serverSocketChannel.bind(new InetSocketAddress(7498));

        FileChannel outputChannel = FileChannel.open(Paths.get("D:\\gdmp.txt"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);
        // 选择器
        Selector selector = Selector.open();

        // 将通道注册到选择器上，并制定监听事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 轮询式获得选择器里的已经准备就绪的事件
        while (selector.select() > 0 ){

            // 获取已经就绪的监听事件
            Iterator<SelectionKey> selectorIterator =  selector.selectedKeys().iterator();

            // 迭代获取
            while (selectorIterator.hasNext()){
                // 获取准备就绪的事件

                SelectionKey key = selectorIterator.next();

                SocketChannel socketChannel = null;
                // 判断是什么事件
                if (key.isAcceptable()){
                    // 或接受就绪，，则获取客户端连接
                    socketChannel = serverSocketChannel.accept();

                    //切换非阻塞方式
                    socketChannel.configureBlocking(false);
                    // 注册到选择器上
                    socketChannel.register(selector,SelectionKey.OP_READ);
                } else if (key.isReadable()){
                    // 获取读就绪通道
                    SocketChannel readChannel = (SocketChannel) key.channel();

                    readChannel.configureBlocking(false);
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);

                    int len = 0;
                    while ( (len = readChannel.read(readBuffer)) != -1){ // 读到buffer里，buffer再写到输出通道
                        readBuffer.flip();
                        System.out.println(new String(readBuffer.array(),0,len));
                        outputChannel.write(readBuffer);
                        readBuffer.clear();
                    }
                    readChannel.close();
                    outputChannel.close();
                }
            }

            // 取消选择键
            selectorIterator.remove();

            /**
             * serverSocketChannel.close();不用关闭的，这是服务器端”
             */

        }
    }
}
